<?php
require_once ('iDBCA.php');
require_once ('entity.php');
require_once ('db.config.php');

class DBCA_mysql implements iDBCA {
    
    const SQL_ROOTDIR = 'inc/sql/mysql/';
    private $update_mode = false;
    private $dbcon = null;
    private $write_dbcon = null;
    private $write_con_params = null;
    private $read_dbcon = null;
    private $read_con_params = null;
    private $inTransaction = false;
    //MYSQL事务嵌套报错规避
    private $numTransaction = 0;
    //保存最后一次查询结果集，update遍历时使用 add by allen.qiang 2015-06-09
    private $this_fetch = array();

    function __construct($w_con_params, $r_con_params) {
        $this->write_con_params = $w_con_params;
        if (empty ( $r_con_params ))
            $this->update_mode = true;
        else
            $this->read_con_params = $r_con_params;
    }

    function __destruct() {
        unset ( $this->write_con_param );
        unset ( $this->read_con_params );
        
        if ($this->inTransaction) {
            if (G_DB_DEBUG){
                throw new Exception ( '存在未决事务' );
            }else{
                //error_log ( '存在未决事务' );
            }
        }
        
        if (isset ( $this->write_dbcon )) {
            $this->write_dbcon->close ();
            unset ( $this->write_dbcon );
        }
        
        if (isset ( $this->read_dbcon )) {
            $this->read_dbcon->close ();
            unset ( $this->read_dbcon );
        }
        
        unset ( $this->dbcon );
    }

    private function connect_write_db() {
        if (is_null ( $this->write_dbcon )) {
            if ($this->write_dbcon = mysqli_init ()) {
                $this->write_dbcon->options ( MYSQLI_INIT_COMMAND, 'SET NAMES ' . G_DB_CHARSET );
                $this->write_dbcon->options ( MYSQLI_OPT_CONNECT_TIMEOUT, 15 );
                if ($this->write_dbcon->real_connect ( $this->write_con_params ['G_DB_SERVERNAME'], $this->write_con_params ['G_DB_USER'], $this->write_con_params ['G_DB_PWD'], $this->write_con_params ['G_DB_DATABASE'], $this->write_con_params ['G_DB_PORT'] )) {
                    $this->dbcon = $this->write_dbcon;
                    return $this->update_mode = true;
                }
            }
            return false;
        } else {
            if (isset ( $this->read_dbcon )) {
                $this->read_dbcon->close ();
                unset ( $this->read_dbcon );
            }
            if ($this->dbcon !== $this->write_dbcon)
                $this->dbcon = $this->write_dbcon;
            return $this->update_mode = true;
        }
    }

    private function connect_read_db() {
        if (is_null ( $this->read_dbcon )) {
            if ($this->read_dbcon = mysqli_init ()) {
                $this->read_dbcon->options ( MYSQLI_INIT_COMMAND, 'SET NAMES ' . G_DB_CHARSET );
                $this->read_dbcon->options ( MYSQLI_OPT_CONNECT_TIMEOUT, 15 );
                if ($this->read_dbcon->real_connect ( $this->read_con_params ['G_DB_SERVERNAME'], $this->read_con_params ['G_DB_USER'], $this->read_con_params ['G_DB_PWD'], $this->read_con_params ['G_DB_DATABASE'], $this->read_con_params ['G_DB_PORT'] )) {
                    $this->dbcon = $this->read_dbcon;
                    return true;
                }
            } else if ($this->dbcon !== $this->read_dbcon)
                $this->dbcon = $this->read_dbcon;
            return true;
        }
    }

    private function select_dbcon($sql = null) {
 		$exe_sql = preg_replace ( "/[ ]+/i", ' ', trim ( $sql ) );
        $exe_command = substr ( $exe_sql, 0, strpos ( $exe_sql, ' ' ) );
    	
        if (strcasecmp ( "SELECT", $exe_command ) == 0 || strcasecmp ( "SHOW", $exe_command ) == 0)
        	$sql_action = 1;  // SELECT
       	else 
       		$sql_action = 2;  // INSERT / UPDATE / DELETE
       		
        if ($this->update_mode || is_null ( $sql )) { // 进入了更新模式，或无sql参数（往往意味着事务调用）
            $this->connect_write_db ();
            return $sql_action; //INSERT / UPDATE / DELETE
        }
        
        if (strcasecmp ( "SELECT", $exe_command ) == 0) {
            $test_forupdate = substr ( $exe_sql, - 10 );
            $test_lockinshare = substr ( $exe_sql, - 18 );
            if (strcasecmp ( "FOR UPDATE", $test_forupdate ) == 0 || strcasecmp ( "LOCK IN SHARE MODE", $test_lockinshare ) == 0) {
                $this->connect_write_db ();
                return $sql_action;
            }
            $this->connect_read_db ();
            return $sql_action; // SELECT
        }
        
        //take write_dbcon as the default
        $this->connect_write_db ();
        return $sql_action;  //INSERT / UPDATE / DELETE
    }

    public function __get($name) {
        if (is_null ( $this->dbcon ))
            return null;
        return $this->dbcon->$name;
    }

    public function beginTransaction() {
    	//MYSQL事务嵌套报错规避
        if ($this->inTransaction) {
            $this->numTransaction ++; return;
        }
        
        $this->update_mode = true;
        $this->select_dbcon ();
        $this->dbcon->autocommit ( false );
        return $this->inTransaction = true;
    }

    public function commit() {
        if (! $this->inTransaction)
            throw new Exception ( '当前数据库连接没有发起事务，无法执行事务命令！' );
	//MYSQL事务嵌套报错规避
        if($this->numTransaction > 0) {
            $this->numTransaction --; return;
        }
        
        $this->update_mode = true;
        $this->select_dbcon ();
        $this->dbcon->commit ();
        $this->dbcon->autocommit ( true );
        $this->inTransaction = false;
        return true;
    }

    public function rollBack() {
        if (! $this->inTransaction)
            throw new Exception ( '当前数据库连接没有发起事务，无法执行事务命令！' );
	//MYSQL事务嵌套报错规避
        if($this->numTransaction > 0) {
            $this->numTransaction --; return;
        }

        $this->update_mode = true;
        $this->select_dbcon ();
        $this->dbcon->rollBack ();
        $this->dbcon->autocommit ( true );
        $this->inTransaction = false;
        return true;
    }

    public function isInTransaction() {
        return $this->inTransaction;
    }

    public function execute($sql) {
        $this->select_dbcon ( $sql );
        return $this->dbcon->query ( $sql );
    }

    public function createStatement($sql) {
        $this->select_dbcon ( $sql );
        return $this->dbcon->prepare ( $sql );
    }

    /*
    	Added by Wanson 2012-03-20
    	一、参数说明：
    		如果还有bind_param传入参数,在$sql参数后面动态加:
    		第一个要动态增加的参数应该是$types,例如"is"
    		后面参数个数和类型与$types相对应
    		完整例子：
    		1、不带参数的调用
    			stmt_query('select * from table');
    		2、带参数的调用
    			stmt_query('select * from table where id=? and name=?',"is",100,"Wanson");
    	二、返回值说明：
    		1、查询到结果 array of entity
    		2、没有查询到结果 null
    */
	public function stmt_query($sql){
    	$sql_action = $this->select_dbcon ( $sql );
        $stmt = $this->dbcon->prepare ( $sql );
        if (!$stmt) 
        	throw new Exception($this->dbcon->error,$this->dbcon->errno);
        $params = func_get_args();
        $params_count = count($params);
        if ($params_count > 2 && !empty($params[1])){
        	if (is_array($params[2])){
        		$ref_params = array($params[1]);
        		foreach ($params[2] as &$bindparam) {
        			$ref_params[] = $bindparam;
        		}
        		call_user_func_array(array($stmt, 'bind_param'), &$ref_params);
        	}else{
        		array_shift($params); //从数组中移除第一个参数$sql，剩余的参数就可以直接传入bind_param的调用
        		call_user_func_array(array($stmt, 'bind_param'),& $params);
        	}
        }
        if (!$stmt->execute()) 
        	throw new Exception($this->dbcon->error,$this->dbcon->errno);
       
        // Modified by Wanson   surpported insert/update/delete  2012-08-09
        if ($sql_action == 1){  // SELECT ACTION
	        $rs = $stmt->get_result();
	        if (!$rs->num_rows)
	        	return null;
	        	
	        while ($obj = $rs->fetch_array(MYSQLI_ASSOC)){
	        	$result_record = new cellBox();
	        	foreach ($obj as $k => &$v) {
                    $result_record->$k = $v;
                }
                $data[] = $result_record;
	        	unset($result_record);
	        }
	        $rs->free();
	        $stmt->close();
	        return $data;
        }
        
        if ($sql_action == 2){ // INSERT / UPDATE / DELETE  ACTION
        	$affectedRows = $stmt->affected_rows;
        	$stmt->close();
        	return $affectedRows;
        }
        
        return false; // unexcept action
        
	}
	
    public function &inc_query($sub_dir, $queryFile, $_params = null) {
        $sqldir = self::SQL_ROOTDIR . $sub_dir . '/';
        $cache = new cacheSQL ( $sqldir );
        $sqlFile = $cache->loadSql ( $queryFile );
        require ($sqlFile);
        $_INC_STMT->close ();
        if (isset ( $_INC_RESULT ))
            return $_INC_RESULT;
        else {
            $_NULL = null;
            return $_NULL;
        }
    }

    public function &inc_load($sub_dir, $queryFile, $_params = null) {
        if ($result = $this->inc_query ( $sub_dir, $queryFile, $_params ))
            return $result [0];
        else {
            $_NULL = null;
            return $_NULL;
        }
    }

    //deprecated 20110412
    public function &preDefinedQuery($queryFile, $_params = null) {
        return $this->inc_query ( 'default', $queryFile, $_params );
    }

    //deprecated 20110412
    public function &preDefinedLoad($queryFile, $_params = null) {
        if ($result = $this->inc_query ( 'default', $queryFile, $_params ))
            return $result [0];
        else {
            $_NULL = null;
            return $_NULL;
        }
    }

    //deprecated 20110412
    /*public function &preDefinedQueryE($entityName,$queryFile,$_params = null){
		$cache = new cacheSQL('inc/sql/mysql/');
		$sqlFile = $cache->loadSql($queryFile,$entityName);
		require($sqlFile);
		$_INC_STMT->close();
		if (isset($_INC_RESULT))
			return $_INC_RESULT;
		else{
			$_NULL = null;
			return $_NULL;
		}
	}*/
    //deprecated 20110412
    /*public function &preDefinedLoadE($entityName,$queryFile,$_params = null){
		if ($result = $this->preDefinedQueryE($entityName,$queryFile,$_params))
			return $result[0];
		else{
			$_NULL = null;
			return $_NULL;
		}
	}*/
    
    public function fetch(entity &$_entity) {
        $sql = 'SELECT * FROM ' . $_entity->getIdentifier () . ' WHERE';
        $fields = $_entity->getFieldsMap ();
        foreach ( $fields as $k => $v ) {
        	
            if ((is_null ( $v ) || $v == '') && (strtoupper($k) == 'UUID' || strtoupper($k) == 'TIMESTAMP'))
                continue;
            if (gettype ( $v ) == 'integer' || gettype ( $v ) == 'double')
                $sql .= " AND $k=$v";
            else {
                if (is_null ( $v ))
                    $sql .= " AND $k is NULL";
                else
                    $sql .= " AND $k='$v'";
            }
        }
        $sql = str_replace ( 'WHERE AND', 'WHERE', $sql );
        $this->select_dbcon ( $sql );
        if (! $result = $this->dbcon->query ( $sql ))
            return null;
        if (! $row = $result->fetch_array ( MYSQLI_ASSOC ))
            return null;


        $array = $this->this_fetch;
        $table = $_entity->getIdentifier();
        $array[$table] = $row;

        $this->this_fetch = $array;

        $result->close (); // upgrade

        return $_entity->cloneFields ( $row );
    }

    public function load(entity &$_entity) {
        if (! $this->fetch ( $_entity )) {
            throw new Exception ( $_entity->getIdentifier () . '---指定记录没有找到！' );
        }
        return true;
    }

    //updated 20130124
	/**
	*@author weifangan
	*通过entity保存或者更新一条记录
	*
	*/
    public function save(entity &$_entity, $sub_dir = 'default') {
		
        $this->update_mode = true;

        $_entity->timeStamp = date ( G_DEFAULT_DATETIME_FORMAT );

        if ($_entity->is_persisted ())
            $action = 'update';
        else
            $action = 'insert';

        try {
			//绑定参数
            $ref_params = array();
			//绑定参数类型
			$bindType = '';
			//如果为插入
            if ($action == 'insert'){
                $_entity->markPersisted ();
                $fields_mapping = $_entity->getFieldsMap();
                $fields = array_keys($fields_mapping);
                foreach ($fields as $k => &$field) {
                    if (is_null($_entity->$field)) {
                        unset($fields[$k]);
                    }
                }               
                $sql = ' INSERT INTO ' . $_entity->getIdentifier() . ' (' . implode(',',
                                $fields) . ') VALUES (';
                
                foreach ($fields as &$f) {
                    $sql .= '?,';
                    if(gettype ( $_entity->$f ) == 'integer'){ 
                    	$bindType .= 'i';
                    }elseif(gettype ($_entity->$f ) == 'double'){
                    	$bindType .= 'd';
                    }elseif (gettype ($_entity->$f ) == 'string'){
                    	$bindType .= 's';
                    }else{
                    	$bindType .= 's';
                    }
                    $ref_params[] = $_entity->$f;
                }
                $sql = substr($sql, 1, -1) . ')';
				//如果为更新
            } elseif ($action == 'update') {

                $sql = 'UPDATE ' . $_entity->getIdentifier() . ' SET ';

                $fields_mapping = $_entity->getFieldsMap();

                $fields = array_keys($fields_mapping);

                foreach ($fields as &$field) {

                    //只更新有变更的字段，不兼容没有fetch的更新操作 add by allen.qiang 2015-06-09
                    //if (is_null($_entity->$field) || $field != 'UUID') {

                    if ($_entity->$field != $this->this_fetch[$_entity->getIdentifier()][$field] && $field != 'UUID') {

                        $sql .= $field . '=?,';

	                    if(gettype ( $_entity->$field) == 'integer'){ 
	                    	$bindType .= 'i';
	                    }elseif(gettype ($_entity->$field ) == 'double'){
	                    	$bindType .= 'd';
	                    }elseif (gettype ($_entity->$field ) == 'string'){
	                    	$bindType .= 's';
	                    }else{
	                    	$bindType .= 's';
	                    }

						$ref_params[] = $_entity->$field;
                    }
                }

                $sql = substr($sql, 0, -1) . ' WHERE uuid=?';

                $bindType .= 's';

                $ref_params[] = $_entity->uuid;
            }

			$bind_params = array($bindType);

			foreach($ref_params as &$ref){
				$bind_params[] = $ref;
			}

			$sql_action = $this->select_dbcon($sql);

            $stmt = $this->dbcon->prepare ( $sql );

			if (!$stmt) 
				 throw new Exception($this->dbcon->error,$this->dbcon->errno);

            call_user_func_array(array($stmt, 'bind_param'),&$bind_params);
			
			if (!$stmt->execute()) 
					throw new Exception($this->dbcon->error,$this->dbcon->errno);
					
            $affectedRows = $stmt->affected_rows;
	    //返回自增长数值
            if ($action == 'insert') $_entity->LAST_INSERT_ID = $stmt->insert_id;

            $stmt->close();
        	return $affectedRows;          
        } catch( Exception $e ) {
            if ($action == 'insert')
                $_entity->unmarkPersisted ();
            throw new Exception ( $e->getMessage () );
        }
    }

    public function delete(entity &$_entity) {
        $this->update_mode = true;
        if ($_entity->is_persisted ()) {
            $sql = 'DELETE FROM ' . $_entity->getIdentifier () . " WHERE uuid='" . $_entity->uuid . "'";
            
            $this->select_dbcon ( $sql );
            $this->dbcon->query ( $sql );
            if ($this->dbcon->affected_rows < 1)
                return false;
            unset ( $_entity );
            return true;
        }
    }
    
    
    /**
     *
     * @param parallelSet $parallelSet
     * @return null
     * @throws Exception
     */
    public function parallelSetOper(parallelSet $parallelSet) {
    	$gofireRequstHandler = new gofireRequestHandler();
    	$param = array();
    	$gofireRequstHandler->req_uri = 'db/query';
    	$gofireRequstHandler->req_method = 'post';
    
    	foreach ($parallelSet->getParallelEntitySet() as $parallelEntity) {
    		if (!$parallelEntity instanceof ParallelEntity) {
    			throw new Exception('bad class of params' . gettype($parallelEntity));
    		}
    		$tmparr = array();
    		$tmparr['Id'] = $parallelEntity->getParalleId();
    		$tmparr['Sql'] = base64_encode($parallelEntity->getSql());
    		$tmparr['Arg'] = $parallelEntity->getArg();
    		$tmparr['KeyField'] = $parallelEntity->getKeyField();
    		$param['req_json'][] = $tmparr;
    		unset($tmparr);
    	}
    
    	$param['combine_condition'] = $parallelSet->getParallelSetExpression();
    
    	$result = $gofireRequstHandler->request_gofire($param);
    
    	if (!$result) {
    		throw new Exception(implode(',', $gofireRequstHandler->getErrStack()));
    	}
    
    	if ($result['HTTP_CODE'] != 200) {
    		//error_log(print_r($result,true));
    		throw new Exception($result['DATA']);
    	}
    
    	$_rs = $result['DATA']['EntityObjects'];
    	if (!$_rs || $result['DATA']['EntityObjectsCount'] == 0) {
    		return null;
    	}
    
    	$retRs = array();
    	foreach ($_rs as $val) {
    		$entity = new stdClass();
    		foreach ($val as $ek => $ev) {
    			$entity->$ek = $ev;
    		}
    		$retRs['data'][] = $entity;
    	}
    	$retRs['total'] = $result['DATA']['EntityObjectsCount'];
    	return $retRs;
    }
}
?>